package com.vcc.service.impl;


import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.vcc.core.exception.NotFoundException;
import com.vcc.core.exception.ProgramException;
import com.vcc.core.util.DateUtil;
import com.vcc.core.util.RedisCustomizeUtils;
import com.vcc.core.util.RegexUtil;
import com.vcc.data.entity.RechargeLog;
import com.vcc.data.entity.Role;
import com.vcc.data.entity.Wallet;
import com.vcc.data.mapper.RechargeLogMapper;
import com.vcc.service.boss.request.rechargeLog.AuditOrderDto;
import com.vcc.service.sa.BossUserUtil;
import com.vcc.service.service.RechargeLogService;
import com.vcc.service.service.UserService;
import com.vcc.service.service.WalletDetailService;
import com.vcc.service.service.WalletService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;

import static com.vcc.data.entity.table.BossUserTableDef.BOSS_USER;
import static com.vcc.data.entity.table.RechargeLogTableDef.RECHARGE_LOG;
import static com.vcc.data.entity.table.UserTableDef.USER;

@Log4j2
@Service
public class RechargeLogServiceImpl extends ServiceImpl<RechargeLogMapper, RechargeLog> implements RechargeLogService {

    @Autowired
    private UserService userService;
    @Autowired
    private WalletService walletService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private WalletDetailService walletDetailService;


    @Override
    public Page<RechargeLog> paging(Role role, Long userId, Page<RechargeLog> pageable, Long targetUserId, Integer useFlag, Integer status, String remark, String startDate, String endDate) {
        QueryChain<RechargeLog> queryChain = queryChain();
        if (role.getHasAllData()) {
            queryChain.leftJoin(USER).on(RECHARGE_LOG.USER_ID.eq(USER.ID)).where(
                    USER.PARENT_ID.eq(userId).or(USER.ID.eq(userId))
            );
            if (RegexUtil.checkObjectIsNotNull(targetUserId)) {
                queryChain.eq(RechargeLog::getUserId, targetUserId);
            }
        } else {
            queryChain.eq(RechargeLog::getUserId, userId);
        }
        if (RegexUtil.checkObjectIsNotNull(useFlag)) {
            queryChain.eq(RechargeLog::getUseFlag, useFlag);
        }
        if (RegexUtil.checkObjectIsNotNull(status)) {
            queryChain.eq(RechargeLog::getStatus, status);
        }
        if (RegexUtil.checkObjectIsNotNull(remark)) {
            queryChain.eq(RechargeLog::getRemark, remark);
        }
        if (RegexUtil.checkObjectIsNotNull(startDate)) {
            queryChain.ge(RechargeLog::getRequestDate, DateUtil.getDayBeginDate(startDate, "yyyy-MM-dd"));
        }
        if (RegexUtil.checkObjectIsNotNull(endDate)) {
            queryChain.le(RechargeLog::getRequestDate, DateUtil.getDayEndDate(endDate, "yyyy-MM-dd"));
        }
        queryChain.orderBy(RechargeLog::getRequestDate, false);
        return queryChain.page(pageable);
    }

    @Override
    public Boolean audit(AuditOrderDto auditOrderDto) {
        Long dealerId = BossUserUtil.getIdNotNull();
        String key = "audit:" + auditOrderDto.getId();
        RLock lock = redissonClient.getLock(key);
        RechargeLog rechargeLog = null;
        try {
            boolean tryLock = lock.tryLock();
            if (!tryLock) {
                throw new ProgramException("当前订单正在审核中，请稍后再试");
            }
            rechargeLog = queryChain().eq(RechargeLog::getId, auditOrderDto.getId()).oneOpt().orElseThrow(() -> new NotFoundException("当前订单无法审核"));
            if (!RedisCustomizeUtils.setIfAbsent("check_usdt_order:" + rechargeLog.getOrderId())) {
                log.info("USDT充值订单 {} 正在被其他线程处理，跳过检查", rechargeLog.getOrderId());
                throw new ProgramException("当前订单正在处理中，请稍后再试");
            }
            //对公打款时如果凭证不为空则检查凭证是否重复
            if (rechargeLog.getUseFlag() != 2 && queryChain().where(RECHARGE_LOG.VOUCHER.eq(auditOrderDto.getVoucher())).exists()) {
                throw new ProgramException("当前订单凭证已存在，请勿重复上传");
            }
            if (dealerId.equals(rechargeLog.getUserId())) {
                throw new ProgramException("无法审核自己的订单");
            }

            if (!BossUserUtil.hasAllData() && !userService.getDealerIdById(rechargeLog.getUserId()).equals(dealerId)) {
                throw new ProgramException("无权审核该订单");
            }
            if (!rechargeLog.getStatus().equals(2) && !rechargeLog.getStatus().equals(3)) {
                throw new ProgramException("当前订单已经审核无法再次审核");
            }
            if (auditOrderDto.getStatus()) {
                if (StringUtils.isBlank(auditOrderDto.getVoucher())) {
                    throw new ProgramException("请填写审核凭证");
                }
                if (StringUtils.isBlank(auditOrderDto.getRemittanceAddress())) {
                    throw new ProgramException("请填写汇款地址");
                }
                Wallet userWallet = walletService.getByUserIdAndCurrencyId(rechargeLog.getUserId(), rechargeLog.getCurrencyId());
                BigDecimal exchangeRate = userWallet.getExchangeRate().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
                BigDecimal tempAmount = rechargeLog.getRemittanceAmount().multiply(exchangeRate);
                BigDecimal amount = rechargeLog.getRemittanceAmount().subtract(tempAmount).setScale(2, RoundingMode.HALF_UP);
                //优先扣除审核用户的钱
                //再增加被审核用户的钱
//                BigDecimal dealerResultAmount = walletService.subtractUsableQuotaById(dealerWallet.getId(), amount, 4, false);
//                walletDetailService.accountOut(rechargeLog.getOrderId(), rechargeLog.getRemark(), amount, dealerResultAmount, 0, dealerWallet.getCurrencyId(), userId);
                BigDecimal userResultAmount = walletService.addUsableQuotaById(userWallet.getId(), amount, 1, false);
                walletDetailService.accountRecharge(rechargeLog.getOrderId(), rechargeLog.getRemark(), amount, userResultAmount, userWallet.getCurrencyId(), rechargeLog.getUserId());
                updateChain().
                        set(RechargeLog::getStatus, 0).
                        set(RechargeLog::getAmount, amount).
                        set(RechargeLog::getAuditDate, new Date()).
                        set(RechargeLog::getAuditUserId, dealerId).
                        set(RechargeLog::getRemittanceAddress, auditOrderDto.getRemittanceAddress()).
                        set(RechargeLog::getVoucher, rechargeLog.getUseFlag() == 2 ? rechargeLog.getVoucher() : auditOrderDto.getVoucher()).
                        set(RechargeLog::getAuditRemark, "审核通过原因" + auditOrderDto.getRemark()).
                        eq(RechargeLog::getId, rechargeLog.getId()).
                        update();
            } else {
                updateChain().
                        set(RechargeLog::getStatus, 1).
                        set(RechargeLog::getAuditDate, new Date()).
                        set(RechargeLog::getAuditUserId, dealerId).
                        set(RechargeLog::getAuditRemark, " 审核驳回原因" + auditOrderDto.getRemark()).
                        eq(RechargeLog::getId, rechargeLog.getId()).
                        update();
            }
            return true;
        } catch (Exception e) {
            if (e instanceof ProgramException || e instanceof NotFoundException) {
                throw e;
            }
            throw new ProgramException("审核订单失败");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
            if (rechargeLog != null) {
                RedisCustomizeUtils.removeKey("check_usdt_order:" + rechargeLog.getOrderId());
            }
        }
    }

    @Override
    public Boolean autoAudit(AuditOrderDto auditOrderDto) {
        String key = "audit:" + auditOrderDto.getId();
        RLock lock = redissonClient.getLock(key);
        try {
            boolean tryLock = lock.tryLock();
            if (!tryLock) {
                throw new ProgramException("当前订单正在审核中，请稍后再试");
            }
            RechargeLog rechargeLog = queryChain().eq(RechargeLog::getId, auditOrderDto.getId()).oneOpt().orElseThrow(() -> new NotFoundException("当前订单无法审核"));
            if (queryChain().where(RECHARGE_LOG.VOUCHER.eq(auditOrderDto.getVoucher())).exists()) {
                throw new ProgramException("当前订单凭证已存在，请勿重复上传");
            }
            if (!rechargeLog.getStatus().equals(2)) {
                throw new ProgramException("当前订单已经审核无法再次审核");
            }
            Wallet userWallet = walletService.getByUserIdAndCurrencyId(rechargeLog.getUserId(), rechargeLog.getCurrencyId());

//                Wallet dealerWallet = walletService.getByUserIdAndCurrencyId(userId, rechargeLog.getCurrencyId());
            BigDecimal exchangeRate = userWallet.getExchangeRate().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
            BigDecimal tempAmount = rechargeLog.getRemittanceAmount().multiply(exchangeRate);
            BigDecimal amount = rechargeLog.getRemittanceAmount().subtract(tempAmount).setScale(2, RoundingMode.HALF_UP);
            //优先扣除审核用户的钱
            //再增加被审核用户的钱
//                BigDecimal dealerResultAmount = walletService.subtractUsableQuotaById(dealerWallet.getId(), amount, 4, false);
//                walletDetailService.accountOut(rechargeLog.getOrderId(), rechargeLog.getRemark(), amount, dealerResultAmount, 0, dealerWallet.getCurrencyId(), userId);
            BigDecimal userResultAmount = walletService.addUsableQuotaById(userWallet.getId(), amount, 1, false);
            walletDetailService.accountRecharge(rechargeLog.getOrderId(), rechargeLog.getRemark(), amount, userResultAmount, userWallet.getCurrencyId(), rechargeLog.getUserId());
            updateChain().
                    set(RechargeLog::getStatus, 0).
                    set(RechargeLog::getAmount, amount).
                    set(RechargeLog::getAuditDate, new Date()).
                    set(RechargeLog::getAuditUserId, null).
                    set(RechargeLog::getRemittanceAddress, auditOrderDto.getRemittanceAddress()).
                    set(RechargeLog::getVoucher, auditOrderDto.getVoucher()).
                    set(RechargeLog::getAuditRemark, auditOrderDto.getRemark()).
                    eq(RechargeLog::getId, rechargeLog.getId()).
                    update();
            return true;
        } catch (Exception e) {
            if (e instanceof ProgramException || e instanceof NotFoundException) {
                throw e;
            }
            throw new ProgramException("审核订单失败");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public Page<RechargeLog> paging(Long userId, Page<RechargeLog> pageable, Long targetUserId, Integer useFlag, Integer status, String remark, String startDate, String endDate, Long dealerId) {
        QueryChain<RechargeLog> queryChain = queryChain();
        queryChain.leftJoin(USER).on(RECHARGE_LOG.USER_ID.eq(USER.ID));
        queryChain.leftJoin(BOSS_USER.as("dealer")).on(USER.BE_LONG_TO_DEALER.eq(BOSS_USER.as("dealer").ID));
        queryChain.leftJoin(BOSS_USER.as("audit")).on(BOSS_USER.as("audit").ID.eq(RECHARGE_LOG.AUDIT_USER_ID));
        queryChain.select(RECHARGE_LOG.ALL_COLUMNS);
        queryChain.select(USER.EMAIL.as("account"));
        queryChain.select(BOSS_USER.as("dealer").EMAIL.as("dealerAccount"));
        queryChain.select(BOSS_USER.as("audit").EMAIL.as("auditAccount"));
        Long bossUserId = BossUserUtil.getIdNotNull();
        if (!BossUserUtil.hasAllData()) {
            queryChain.and(
                    BOSS_USER.as("dealer").ID.eq(bossUserId)
                            .or(BOSS_USER.as("dealer").PARENT_ID.eq(bossUserId))
            );
        } else {
            if (RegexUtil.checkObjectIsNotNull(dealerId)) {
                queryChain.and(
                        BOSS_USER.as("dealer").ID.eq(dealerId)
                                .or(BOSS_USER.as("dealer").PARENT_ID.eq(dealerId))
                );
            }
        }
        if (RegexUtil.checkObjectIsNotNull(targetUserId)) {
            queryChain.eq(RechargeLog::getUserId, targetUserId);
        }
        if (RegexUtil.checkObjectIsNotNull(useFlag)) {
            queryChain.eq(RechargeLog::getUseFlag, useFlag);
        }
        if (RegexUtil.checkObjectIsNotNull(status)) {
            queryChain.eq(RechargeLog::getStatus, status);
        }
        if (RegexUtil.checkObjectIsNotNull(remark)) {
            queryChain.eq(RechargeLog::getRemark, remark);
        }
        if (RegexUtil.checkObjectIsNotNull(startDate)) {
            queryChain.ge(RechargeLog::getRequestDate, DateUtil.getDayBeginDate(startDate, "yyyy-MM-dd"));
        }
        if (RegexUtil.checkObjectIsNotNull(endDate)) {
            queryChain.le(RechargeLog::getRequestDate, DateUtil.getDayEndDate(endDate, "yyyy-MM-dd"));
        }
        queryChain.orderBy(RechargeLog::getRequestDate, false);
        return queryChain.page(pageable);
    }


    @Override
    public Boolean verifyAmount(BigDecimal amount) {
        return queryChain()
                .eq(RechargeLog::getAmount, amount)
                .eq(RechargeLog::getStatus, 2)
                .between(RechargeLog::getRequestDate, new Date(), cn.hutool.core.date.DateUtil.offsetMinute(new Date(), -30))
                .exists();
    }

    @Override
    public Boolean existsByPendingOrder(Long userId) {
        return queryChain()
                .eq(RechargeLog::getUserId, userId)
                .eq(RechargeLog::getStatus, 2)
                .exists();
    }

    @Override
    public Boolean updateStatusAndRemark(String orderId, Integer status, String remark) {
        return updateChain()
                .set(RechargeLog::getStatus, status)
                .set(RechargeLog::getRemark, remark)
                .eq(RechargeLog::getOrderId, orderId)
                .update();
    }

    @Override
    public Boolean updateStatusAndAuditRemark(String orderId, Integer status, String auditRemark) {
        return updateChain()
                .set(RechargeLog::getStatus, status)
                .set(RechargeLog::getAuditRemark, auditRemark)
                .eq(RechargeLog::getOrderId, orderId)
                .update();
    }

    @Override
    public RechargeLog getByOrderId(String orderId) {
        return queryChain()
                .eq(RechargeLog::getOrderId, orderId)
                .oneOpt()
                .orElseThrow(() -> new NotFoundException("充值订单不存在"));
    }

    @Override
    public Boolean existsByVoucher(String voucher) {
        return queryChain()
                .eq(RechargeLog::getVoucher, voucher)
                .exists();
    }

    @Override
    public List<JSONObject> statistics() {
        //计算昨天的时间 段 00:00:00 - 23:59:59 账户充值 成功的总金额
        QueryChain<RechargeLog> queryChain = queryChain().select(
                "user_id as userId",
                "COUNT(*) AS totalCount",
                "SUM(CASE WHEN STATUS = 0 THEN 1 ELSE 0 END) AS successCount",
                "SUM(CASE WHEN status = 0 THEN amount ELSE 0 END) AS successAmount"
        );
        queryChain.where(RECHARGE_LOG.AUDIT_DATE.between(
                DateUtil.getDayBeginDate(DateTime.now().offset(DateField.DAY_OF_MONTH, -1)),
                DateUtil.getDayEndDate(DateTime.now().offset(DateField.DAY_OF_MONTH, -1))));
        queryChain.groupBy(RECHARGE_LOG.USER_ID);
        return mapper.selectListByQueryAs(queryChain, Row.class).stream().map(JSONObject::new).toList();
    }

    @Override
    public List<JSONObject> statistics(DateTime startDate, DateTime endDate) {
        QueryChain<RechargeLog> queryChain = queryChain().select(
                "user_id as userId",
                "COUNT(*) AS totalCount",
                "SUM(CASE WHEN STATUS = 0 THEN 1 ELSE 0 END) AS successCount",
                "SUM(CASE WHEN status = 0 THEN amount ELSE 0 END) AS successAmount"
        );
        queryChain.where(RECHARGE_LOG.AUDIT_DATE.between(startDate, endDate));
        queryChain.groupBy(RECHARGE_LOG.USER_ID);
        return mapper.selectListByQueryAs(queryChain, Row.class).stream().map(JSONObject::new).toList();
    }

    @Override
    public List<JSONObject> statistics(DateTime startDate, DateTime endDate, Long userId, Long dealerId) {
        QueryChain<RechargeLog> queryChain = queryChain().select(
                "DATE_FORMAT(vcc_recharge_log.audit_date, '%Y-%m-%d') as date",
                "COUNT(vcc_recharge_log.id) AS totalCount",
                "SUM(CASE WHEN vcc_recharge_log.STATUS = 0 THEN 1 ELSE 0 END) AS successCount",
                "SUM(CASE WHEN vcc_recharge_log.status = 0 THEN amount ELSE 0 END) AS successAmount"
        );
        queryChain.where(RECHARGE_LOG.AUDIT_DATE.between(startDate, endDate));
        queryChain.groupBy("DATE_FORMAT(vcc_recharge_log.audit_date, '%Y-%m-%d')");
        if (userId != null) {
            queryChain.and(RECHARGE_LOG.USER_ID.eq(userId));
        }
        if (dealerId != null) {
            queryChain.leftJoin(USER).on(RECHARGE_LOG.USER_ID.eq(USER.ID));
            queryChain.leftJoin(BOSS_USER).on(USER.BE_LONG_TO_DEALER.eq(BOSS_USER.ID));
            queryChain.and(BOSS_USER.ID.eq(dealerId).or(BOSS_USER.PARENT_ID.eq(dealerId)));
        }
        return mapper.selectListByQueryAs(queryChain, Row.class).stream().map(JSONObject::new).toList();
    }

    @Override
    public JSONObject statisticsByDates(DateTime startDate, DateTime endDate) {
        QueryChain<RechargeLog> queryChain = queryChain().select(
                "COUNT(*) AS totalCount",
                "SUM(CASE WHEN STATUS = 0 THEN 1 ELSE 0 END) AS successCount",
                "SUM(CASE WHEN status = 0 THEN amount ELSE 0 END) AS successAmount"
        );
        queryChain.where(RECHARGE_LOG.AUDIT_DATE.between(startDate, endDate));
        return getOneAs(queryChain, JSONObject.class);
    }
}
